// $Id: request.c 94 2011-10-19 12:48:16Z cedric.shih@gmail.com $
/*
 * Copyright 2011 Cedric Shih (cedric dot shih at gmail dot com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include <zcbuffer.h>

#include "log.h"
#include "../request.h"
#include "headers.h"

#include "request.h"

int niortsp_request_decoder_parse_line(
		struct niortsp_request_decoder *decoder,
		struct zcbuffer *buffer);

struct niortsp_request_decoder *niortsp_request_decoder_new(void)
{
	struct niortsp_request_decoder *codec;

	niortsp_debug("constructing");

	if (!(codec = calloc(1, sizeof(*codec)))) {
		niortsp_error("failed to calloc");
		goto exception;
	}

	if (!(codec->decoder = niortsp_headers_decoder_new())) {
		niortsp_error("failed to create header decoder");
		goto exception;
	}

	return codec;

exception:
	niortsp_request_decoder_free(codec);
	return NULL;
}

void niortsp_request_decoder_free(struct niortsp_request_decoder *codec)
{
	niortsp_debug("destructing: %p", codec);

	if (!(codec)) {
		return;
	}

	if (codec->decoder) {
		niortsp_headers_decoder_free(codec->decoder);
	}

	if (codec->request) {
		niortsp_request_free(codec->request);
	}

	free(codec);
}

int niortsp_request_decoder_reset(struct niortsp_request_decoder *decoder,
		struct zcbuffer *buffer)
{
	int rc;

	niortsp_debug("resetting");

	if (decoder->request) {
		niortsp_request_free(decoder->request);
	}

	if (!(decoder->request = niortsp_request_new(buffer))) {
		rc = ENOMEM;
		niortsp_error("failed to create request");
		goto finally;
	}

	decoder->state = NIORTSP_REQUEST_LINE;
//	decoder->position = position;
	decoder->offset1 = decoder->offset2 = -1;
	zcbuffer_clear(buffer);
	rc = 0;

finally:
	return rc;
}

int niortsp_request_decoder_parse(
		struct niortsp_request_decoder *decoder,
		struct zcbuffer *buffer, struct niortsp_request **request)
{
	int rc;
	struct niortsp_headers *headers;

	if (zcbuffer_remaining(buffer) <= 0) {
		rc = ENODATA;
		niortsp_error("nothing to parse");
		goto finally;
	}

	while (decoder->state != NIORTSP_REQUEST_CONTENT &&
			zcbuffer_remaining(buffer) > 0) {
		niortsp_debug("parsing at state %d: remaining=%d, position=%d",
				decoder->state, zcbuffer_remaining(buffer),
				zcbuffer_position(buffer));

		switch (decoder->state) {
		case NIORTSP_REQUEST_LINE:
			niortsp_debug("starting parse: %d", zcbuffer_position(buffer));

			if ((rc = niortsp_request_decoder_parse_line(
					decoder, buffer))) {
				niortsp_error("failed to parse first line");
				goto finally;
			}

			break;
		case NIORTSP_REQUEST_HEADERS:
			if ((rc = niortsp_headers_decoder_parse(
					decoder->decoder, buffer, &headers))) {
				niortsp_error("failed to parse headers: %s",
						strerror(rc));
				goto finally;
			}

			if (!headers) {
				break;
			}

			decoder->state = NIORTSP_REQUEST_CONTENT;
			break;
		default:
			niortsp_error("unexpected state: %d", decoder->state);
			rc = -1;
			goto finally;
		}
	}

	if (decoder->state == NIORTSP_REQUEST_CONTENT) {
		*request = decoder->request;
	} else {
		*request = NULL;
	}

	rc = 0;

finally:
	return rc;
}

int niortsp_request_decoder_parse_line(
		struct niortsp_request_decoder *decoder,
		struct zcbuffer *buffer)
{
	int rc, b;
	size_t offset;

	niortsp_trace("parsing line");

	while (zcbuffer_remaining(buffer) > 0) {
		if (decoder->offset1 == -1) {
			niortsp_trace("marking offset at: %d",
					zcbuffer_position(buffer));
			decoder->offset1 = zcbuffer_position(buffer);
		}

		if ((rc = zcbuffer_get(buffer, &b))) {
			niortsp_error("failed to get: %s", strerror(rc));
			goto finally;
		}

		if ((b < ' ' || b > '~') && (b != '\r' && b != '\n')) {
			niortsp_error("invalid encoding");
			rc = -1;
			goto finally;
		}

		niortsp_trace("processing: %d", b);

		if (!decoder->request->method) {
			if (b == ' ') {
				offset = zcbuffer_position(buffer) - 1;

				niortsp_trace("hit end of method: %d", offset);

				if ((rc = niortsp_request_nset_method(
						decoder->request,
						(char *)zcbuffer_array(buffer) + decoder->offset1,
						offset - decoder->offset1))) {
					niortsp_error("failed to set method: "
							"%s",
							strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else if (!decoder->request->uri) {
			if (b == ' ') {
				offset = zcbuffer_position(buffer) - 1;

				niortsp_trace("hit end of uri: %d", offset);

				if ((rc = niortsp_request_nset_uri(
						decoder->request,
						(char *)zcbuffer_array(buffer) + decoder->offset1,
						offset - decoder->offset1))) {
					niortsp_error("failed to set uri: %s",
							strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else if (!decoder->request->protocol) {
			if (b == '/') {
				offset = zcbuffer_position(buffer) - 1;

				niortsp_trace("hit end of protocol: %d",
						offset);

				if ((rc = niortsp_request_nset_protocol(
						decoder->request,
						(char *)zcbuffer_array(buffer) + decoder->offset1,
						offset - decoder->offset1))) {
					niortsp_error("failed to set protocol:"
							" %s",
							strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else if (!decoder->request->version.string) {
			if (b == '\r') {
				offset = zcbuffer_position(buffer) - 1;

				niortsp_trace("hit end of version: %d", offset);

				if ((rc = niortsp_request_nset_version(
						decoder->request,
						(char *)zcbuffer_array(buffer) + decoder->offset1,
						offset - decoder->offset1))) {
					niortsp_error("failed to set version: "
							"%s",
							strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else {
			if (b != '\n') {
				niortsp_error("unexpected end char: %d", b);
				rc = -1;
				goto finally;
			}

			niortsp_trace("hit end of first line");

			if ((rc = niortsp_headers_decoder_reset(
					decoder->decoder,
					decoder->request->input_headers))) {
				niortsp_error("failed to setup header decoder");
				goto finally;
			}

			decoder->offset1 = -1;
			decoder->state = NIORTSP_REQUEST_HEADERS;
			break;
		}
		niortsp_trace("continuing");
	}

	rc = 0;

finally:
	return rc;
}
